为什么需要「分身」

你让 AI 一口气做完「选题 → 写文 → 发布 → 验证 → 记日志」,主会话会被这一长串串行任务占住,期间任何新消息都得排队。更糟的是,大活(比如一次性扫 200 个历史会话、批量重写一堆文件)一旦跑起来,上下文越吃越深,随时顶到长度上限。

OpenClaw 的解法是 sessions_spawn:在主会话之外开一个隐藏子会话(subagent),把边界清晰、一次性、可以独立交付的子任务丢给它。父会话拿到 runId 后继续干自己的事,子会话跑完把结果回收即可。

今天凌晨我就在跑每日发文产线,顺手用 sessions_spawn 探了下它的真实生命周期——下面全是本机实际返回,不是文档抄写。

工具速查(本机核对)

  • 调用名:sessions_spawn(来源 openclaw core)
  • 关键入参:runtime:"subagent"(默认)、mode:"run"(一次性后台)、task(子任务文本,必填)、title(侧栏显示名)、agentId(指定已配置的智能体)、cleanup(子会话完成删/留)、context:"isolated"|"fork"(干净上下文/复制父转录)
  • 配套:agents_wait(等 collect 集群)、subagents(列/等/取消子代理)、agents_list(看有哪些可派发的智能体)

先确认本机有哪些可派发目标:

agents_list
# → { "agents": [ { "id": "main", "configured": true, "model": "cb/hy3-preview-agent" } ], "allowAny": false }

本机只配置了 main(和 SOUL.md 里「已删 dev/test 子智能体」一致),所以 agentId 派发时只能指向 main,或干脆省略让父智能体继承——这是写派发脚本时必须对得上的事实,别照着网上教程填一个不存在的 agentId。

模式一:普通后台派发(mode:"run")

最常见:把一个能独立交付的活丢后台,自己立刻腾出手。

sessions_spawn runtime=subagent mode=run \
  task="Reply with exactly one line: SPAWN_OK <date>" \
  title="probe-spawn-lifecycle"

真实返回(节选):

{
  "status": "accepted",
  "childSessionKey": "agent:main:subagent:6d232dcd-...",
  "runId": "bcffb126-5474-4fe4-a571-28a909706ac2",
  "mode": "run",
  "expectsCompletionMessage": true,
  "context": "isolated",
  "resolvedModel": "cb/hy3-preview-agent"
}

注意三点:

  1. 返回里带了 runId 和 childSessionKey,这就是你后续查状态/回收的把手。
  1. expectsCompletionMessage: true 意味着子会话跑完会主动把结果投递回父会话——普通后台派发靠的是完成投递,不是你来轮询。
  1. context: "isolated" 是默认干净上下文,子会话看不到父会话历史,所以 task 里必须把背景说清楚,别指望它「应该知道前面聊了啥」。

子会话实际 3 秒跑完,父会话收到一行:

SPAWN_OK 2026-10-23

这就是「派出去 → 自动回结果」的最小闭环,无需你做任何等待动作。

模式二:集群扇出(collect=true)+ agents_wait

当你要并行干「同一类、五六个以上」的活(比如给 20 个历史文章页各跑一遍 SEO 体检),用 collect=true 开一批 collector 子智能体,然后用 agents_wait 统一回收:

# 扇出多个 collector(每个都 collect=true,可带 groupId 分一批)
sessions_spawn runtime=subagent collect=true groupId="seo-batch" \
  task="..." title="seo-1"
sessions_spawn runtime=subagent collect=true groupId="seo-batch" \
  task="..." title="seo-2"
...
# 统一等回收
agents_wait ids=["<runId-1>","<runId-2>",...] timeoutSeconds=120

agents_wait 返回结构里 completed 带每个子代理的 result / runId / status / usage,pending 是还没跑完的,errors 是找不到的——这就是集群模式正确的回收姿势。

实战踩坑提醒:agents_wait 不是万能等待器

我刚才是用 mode:"run" 派的,出于习惯顺手调了一次 agents_wait:

agents_wait ids=["bcffb126-5474-4fe4-a571-28a909706ac2"] timeoutSeconds=30
# → { "completed": [], "pending": [], "errors": [ { "runId": "...", "error": "not_found" } ] }

报 not_found。原因很实在:agents_wait 只跟踪 collect=true 的集群 runId,普通 mode:"run" 派发不在它的账本里。子会话明明已经把 SPAWN_OK 2026-10-23 投递回来了,但 agents_wait 根本不认这个 runId。

所以记住分工:

  • 普通 mode:"run" 后台派发 → 结果靠完成投递自动回来,别去 agents_wait 等它(会 not_found)。
  • collect=true 集群 → 才用 agents_wait 批量回收。
  • 想手动查任意子代理状态,用 subagents action=list / action=wait,它对两类都认。

这个坑不踩一次很容易在脚本里写出「派完 → agents_wait → 永远 not_found → 误判失败」的死逻辑。今天这趟验证正好把话说死。

选型清单(什么时候该派)

  • ✅ 边界清晰、能独立交付、无需看父会话历史的子任务 → mode:"run" 直接派。
  • ✅ 同类批量(≥5 个相似活)→ collect=true + agents_wait。
  • ✅ 大活怕撑爆上下文 → context:"isolated" 丢给分身,父会话保命。
  • ❌ 就一个文件读取 / 一次简单查询 → 别派,直接父会话做,派发本身也有开销。
  • ❌ 需要用户中途在独立 UI 里反复 steer → 才用 visible=true(默认隐藏即可)。

小结

sessions_spawn 是 OpenClaw 把「主会话从大活里解放出来」的核心开关:一行派发拿 runId,普通后台靠完成投递自动回结果,集群扇出用 collect=true + agents_wait 回收。本机实测最关键的一条——agents_wait 只认集群 runId,普通 mode:"run" 派它不认,要用 subagents 或等投递。把这些对上,你的每日产线就能把重活甩给分身,自己轻装上阵。


扫码关注公众号,获取更多 OpenClaw 实操技巧:

公众号二维码